﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;

using Ingenious.Graph;
using Ingenious.GraphStore;
using Ingenious.MVVM;
using System.Collections.ObjectModel;
using System.Diagnostics;
using MongoDB.Driver;
using MongoDB.Bson;
using System.IO;

namespace Ingenious.Core.TestHarness
{
    #region Supporting Classes
    public class GenericType : MarkupExtension
    {
        public Type BaseType { get; set; }
        public Type[] InnerTypes { get; set; }

        public GenericType() { }
        public GenericType(Type baseType, params Type[] innerTypes)
        {
            BaseType = baseType;
            InnerTypes = innerTypes;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            Type result = BaseType.MakeGenericType(InnerTypes);
            return result;
        }
    }

    
    public class GraphViewModel<TTripleId> : ViewModelBase<INode<TTripleId>>
    {
        private Graph<TTripleId> m_graph;
        private readonly NodeViewModelList<TTripleId> m_nodeViewModelList;

        private ITreeNodeViewModelItem m_selectedNodeItem;

        #region Object Lifetime
        public GraphViewModel(Graph<TTripleId> graph, Uri topLevelNodeSubjectUri)
        {
            if (graph == null)
            {
                throw new ArgumentNullException("graph");
            }

            m_graph = graph;

            m_nodeViewModelList = new NodeViewModelList<TTripleId>(this);
            m_nodeViewModelList.Add(new NodeViewModelItem<TTripleId>(this, graph.GraphNodes[topLevelNodeSubjectUri]));
        }
        #endregion

        #region Properties
        public ITreeNodeViewModelItem SelectedNodeItem
        {
            get { return m_selectedNodeItem; }

            set
            {
                if (m_selectedNodeItem != value)
                {
                    m_selectedNodeItem = value;
                    NotifyOfPropertyChange(() => SelectedNodeItem);
                }
            }
        }

        public NodeViewModelList<TTripleId> Nodes
        {
            get { return m_nodeViewModelList; }
        }
        #endregion
    }
    #endregion

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MemoryGraphProxy<Guid> m_memoryGraphProxy;
        private MongoGraphProxy m_mongoGraphProxy;

        #region Supporting Classes
        // GUID based memory graph connection
        private class GuidMemoryGraphStoreConnectionInfo : MemoryGraphStoreConnectionInfo<Guid>
        {
            public override Type GraphStoreType
            {
                get { return typeof(GuidMemoryGraphStore); }
            }
        }

        // GUID based memory graph store
        private class GuidMemoryGraphStore : MemoryGraphStore<Guid>
        {
            #region Object Lifetime
            public GuidMemoryGraphStore(MemoryGraphProxy<Guid> proxy, GuidMemoryGraphStoreConnectionInfo graphStoreConnectionInfo)
                : base(proxy, graphStoreConnectionInfo)
            {
            }
            #endregion

            public override Guid CreateNewTripleId()
            {
                return Guid.NewGuid();
            }
        }
        #endregion

        #region Object Lifetime
        public MainWindow()
        {
            InitializeComponent();

            Loaded += new RoutedEventHandler(OnLoaded);
            Closed += new EventHandler(OnClosed);
        }
        #endregion

        #region Loaded
        private void LogOutput(string errorMessage)
        {
            ((ObservableCollection<string>)ErrorLog.ItemsSource).Add(errorMessage);
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            bool success = true;

            // Set this to true to test writing/reading a model to and from a MongoDB instance
            bool testMongoDB = false;
            bool testDublinCore = true;

            m_memoryGraphProxy = null;
            m_mongoGraphProxy = null;

            ErrorLog.ItemsSource = new ObservableCollection<string>();

            Stopwatch stopwatch = new Stopwatch();

            LogOutput("Create and connect to memory graph store...");

            stopwatch.Reset();
            stopwatch.Start();

            // Test connecting to a memory store
            try
            {
                // Start with a basic memory graph proxy. The UI thread synchronization context
                // is informed of property and collection changes.
                m_memoryGraphProxy = new MemoryGraphProxy<Guid>();
                GuidMemoryGraphStoreConnectionInfo connectionInfo = new GuidMemoryGraphStoreConnectionInfo();

                m_memoryGraphProxy.Connect(connectionInfo);
            }
            catch (Exception ex)
            {
                LogOutput("Proxy Connection Error: " + ex.Message + " " + ex.StackTrace);
                success = false;
            }

            stopwatch.Stop();
            LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

            if (success)
            {
                LogOutput("Create 'TestGraph' graph...");

                stopwatch.Reset();
                stopwatch.Start();

                NamespaceUri ldtNs = new NamespaceUri("http://www.linkeddatatools.com", "ld");
                NamespaceUri tpsNs = new NamespaceUri("http://www.mytestscope.com", "ts");

                // Test fetching/creating a new graph
                Graph<Guid> graph = Graph<Guid>.Get(m_memoryGraphProxy, "TestGraph");

                try
                {
                    // Test creating a transaction
                    using (new GraphScope<Guid>(graph, GraphLock.Write))
                    {
                        dynamic node = graph.GetNode("example1", ldtNs);
                        dynamic node2 = graph.GetNode("example2", ldtNs);
                        dynamic node3 = graph.GetNode("example3", ldtNs);

                        // Test creating a URI prefix scope
                        using (new NamespaceUriScope(tpsNs))
                        {
                            // Create a separate test node with a literal predicate
                            node.testProperty = "hello";

                            // Create a test reference (node) predicate
                            node.testProperty2 = node2;

                            node2.childTestProperty = node3;

                            node2.childTestProperty2 = "bonjour";

                            // TODO: If setting a property which is a POCO rather than a node, wrap the
                            // POCO using the Node<> POCO constructor

                            // Set the test node as a predicate of the first node in the graph
                            node2.testProperty3 = node;
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogOutput("Nodes Model Error: " + ex.Message + " " + ex.StackTrace);    
                }

                stopwatch.Stop();
                LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                // Test serializing to triples
                LogOutput("Serializing graph to triples...");

                stopwatch.Reset();
                stopwatch.Start();
                List<Triple<Guid>> triples = graph.ToTriples();

                foreach (Triple<Guid> triple in triples)
                {
                    LogOutput(triple.ToString());
                }

                stopwatch.Stop();
                LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                LogOutput("Serializing triples to Turtle document...");

                stopwatch.Reset();
                stopwatch.Start();
                TurtleSerializer turtleSerializer = new TurtleSerializer();

                NamespaceUriCollection namespaceUriCollection = new NamespaceUriCollection();

                foreach (NamespaceUri namespaceUri in graph.NamespaceUris)
                {
                    namespaceUriCollection.Add(namespaceUri);
                }

                string turtleDoc = turtleSerializer.ToRdfDocument(namespaceUriCollection, triples.OfType<Triple>().ToList());

                stopwatch.Stop();

                LogOutput(turtleDoc);
                LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                LogOutput("Deserializing Turtle document back to triples (round trip)...");

                stopwatch.Reset();
                stopwatch.Start();

                IList<Triple> roundTripTriples = null;
                string error = null;

                if (!turtleSerializer.FromRdfDocument(turtleDoc, out namespaceUriCollection, out roundTripTriples, out error))
                {
                    LogOutput(error);
                }
                else
                {
                    stopwatch.Stop();

                    IList<Triple<Guid>> idTriples = new List<Triple<Guid>>();

                    foreach (Triple triple in roundTripTriples)
                    {
                        if (triple.IsLiteral)
                        {
                            if (triple.LanguageTag != null)
                            {
                                idTriples.Add(new Triple<Guid>(triple.Subject, triple.Predicate, triple.Value, triple.LanguageTag));
                            }
                            else if (triple.LiteralType != null)
                            {
                                idTriples.Add(new Triple<Guid>(triple.Subject, triple.Predicate, triple.Value, triple.LiteralType));
                            }
                            else
                            {
                                idTriples.Add(new Triple<Guid>(triple.Subject, triple.Predicate, triple.Value));
                            }
                        }
                        else
                        {
                            idTriples.Add(new Triple<Guid>(triple.Subject, triple.Predicate, triple.Object));
                        }
                    }

                    LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                    LogOutput("Recreate original graph from triples list...");

                    stopwatch.Reset();
                    stopwatch.Start();

                    try
                    {
                        graph = Graph<Guid>.FromTriples(m_memoryGraphProxy, "New Test Graph", namespaceUriCollection, idTriples);
                    }
                    catch
                    {
                    }

                    stopwatch.Stop();

                    LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                    // Wrap the node model in a graph view model for display in user interface
                    GraphViewModel<Guid> graphViewModel = new GraphViewModel<Guid>(graph, new Uri("http://www.linkeddatatools.com/example1"));
                    DataContext = graphViewModel;
                }
            }
            
            if (testMongoDB)
            {
                LogOutput("Connect to MongoDB graph data store...");

                stopwatch = new Stopwatch();
                stopwatch.Start();

                MongoGraphProxy proxy = null;

                // Create a Mongo DB connection
                MongoServerSettings mongoServerSettings = new MongoServerSettings();
                mongoServerSettings.Server = new MongoServerAddress("localhost");

                MongoGraphStoreConnectionInfo mongoConnectionInfo = new MongoGraphStoreConnectionInfo("mongodb://localhost", mongoServerSettings);

                try
                {
                    proxy = new MongoGraphProxy();
                    proxy.Connect(mongoConnectionInfo);
                }
                catch (Exception ex)
                {
                }

                stopwatch.Stop();
                LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                stopwatch.Reset();

                LogOutput("Create 'TestMongo' graph...");
                stopwatch.Start();

                try
                {
                    NamespaceUri ldtNs = new NamespaceUri("http://www.linkeddatatools.com/", "ld");
                    NamespaceUri tpsNs = new NamespaceUri("http://www.mytestscope.com", "ts");

                    MongoGraphStore mongoGraphStore = new MongoGraphStore(proxy, mongoConnectionInfo);

                    // Test fetching/creating a new graph
                    Graph<ObjectId> graph = Graph<ObjectId>.Get(proxy, "TestMongo");

                    // Test creating a transaction
                    using (new GraphScope<ObjectId>(graph, GraphLock.Write))
                    {
                        dynamic node = graph.GetNode("example1", ldtNs);
                        dynamic node2 = graph.GetNode("example2", ldtNs);
                        dynamic node3 = graph.GetNode("example3", ldtNs);

                        // Test creating a URI prefix scope
                        using (new NamespaceUriScope(tpsNs))
                        {
                            // Create a separate test node with a literal predicate
                            node.testProperty = "hello";

                            // Create a test reference (node) predicate
                            node.testProperty2 = node2;

                            node2.childTestProperty = node3;

                            node2.childTestProperty2 = "bonjour";

                            // TODO: If setting a property which is a POCO rather than a node, wrap the
                            // POCO using the Node<> POCO constructor

                            // Set the test node as a predicate of the first node in the graph
                            node.testProperty3 = node;
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogOutput(ex.Message);
                }

                stopwatch.Stop();
                LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                try
                {
                    // Test delay loading using tree view
                    MongoGraphProxy mongoGraphProxy = new MongoGraphProxy();
                    mongoGraphProxy.Connect(mongoConnectionInfo);
                }
                catch
                {
                }

                try
                {
                    NamespaceUri ldtNs2 = new NamespaceUri("http://www.linkeddatatools.com/", "ld");

                    MongoGraphStore mongoGraphStore = new MongoGraphStore(proxy, mongoConnectionInfo);

                    // Test fetching/creating a new graph
                    Graph<ObjectId> graph = Graph<ObjectId>.Get(proxy, "TestMongo");

                    // Wrap the node model in a graph view model for display in user interface
                    GraphViewModel<ObjectId> graphViewModel = new GraphViewModel<ObjectId>(graph, new Uri("http://www.linkeddatatools.com/example1"));
                    DataContext = graphViewModel;
                }
                catch
                {
                }
            }

            if (testDublinCore)
            {
                try
                {
                    LogOutput("Deserialize dcterms.ttl core document into triples...");
                    stopwatch.Start();

                    // Test loading the DCMI core terms from turtle into a model
                    string dcmiCoreRdf = File.ReadAllText("dcterms.ttl");

                    TurtleSerializer turtleSerializer = new TurtleSerializer();

                    NamespaceUriCollection namespaceUriCollection = null;
                    IList<Triple> triples = null;
                    string error = null;

                    if (!turtleSerializer.FromRdfDocument(dcmiCoreRdf, out namespaceUriCollection, out triples, out error))
                    {
                        LogOutput("Error reported by serializer: " + error);
                    }

                    stopwatch.Stop();

                    LogOutput("Deserialized " + triples.Count + " triple(s) from document.");
                    LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                    LogOutput("Write DCMI ontology to a mongo DB node model...");

                    stopwatch.Restart();

                    MongoGraphProxy proxy = null;

                    // Create a Mongo DB connection
                    MongoServerSettings mongoServerSettings = new MongoServerSettings();
                    mongoServerSettings.Server = new MongoServerAddress("localhost");

                    MongoGraphStoreConnectionInfo mongoConnectionInfo = new MongoGraphStoreConnectionInfo("mongodb://localhost", mongoServerSettings);

                    try
                    {
                        proxy = new MongoGraphProxy();
                        proxy.Connect(mongoConnectionInfo);
                    }
                    catch (Exception ex)
                    {
                        string helper = ex.Message;
                        string helper2 = helper.ToString();
                    }

                    stopwatch.Stop();
                    LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");

                    stopwatch.Reset();

                    LogOutput("Create 'Dublin Core' ontology graph...");
                    stopwatch.Restart();

                    try
                    {
                        MongoGraphStore mongoGraphStore = new MongoGraphStore(proxy, mongoConnectionInfo);

                        // TODO: Need to output triples with ID from serializer!
                        IList<Triple<ObjectId>> idTriples = new List<Triple<ObjectId>>();

                        foreach (Triple triple in triples)
                        {
                            if (triple.IsLiteral)
                            {
                                if (triple.LanguageTag != null)
                                {
                                    idTriples.Add(new Triple<ObjectId>(triple.Subject, triple.Predicate, triple.Value, triple.LanguageTag));
                                }
                                else if (triple.LiteralType != null)
                                {
                                    idTriples.Add(new Triple<ObjectId>(triple.Subject, triple.Predicate, triple.Value, triple.LiteralType));
                                }
                                else
                                {
                                    idTriples.Add(new Triple<ObjectId>(triple.Subject, triple.Predicate, triple.Value));
                                }
                            }
                            else
                            {
                                idTriples.Add(new Triple<ObjectId>(triple.Subject, triple.Predicate, triple.Object));
                            }
                        }

                        // Test fetching/creating a new graph
                        Graph<ObjectId> graph = Graph<ObjectId>.FromTriples(proxy, "TestDublinCore", namespaceUriCollection, idTriples);

                        // Create viewmodel/view
                        GraphViewModel<ObjectId> graphViewModel = new GraphViewModel<ObjectId>(graph, new Uri("http://purl.org/dc/terms/Agent"));
                        DataContext = graphViewModel;
                    }
                    catch (Exception ex)
                    {
                        LogOutput("Unexpected error occurred: " + ex.Message);
                    }

                    stopwatch.Stop();

                    LogOutput("Took " + stopwatch.Elapsed.ToString() + ".");
                }
                catch (Exception ex)
                {
                    LogOutput(ex.Message);
                }
            }
        }

        private void OnClosed(object sender, EventArgs e)
        {
            if (m_memoryGraphProxy != null)
            {
                m_memoryGraphProxy.Dispose();
                m_memoryGraphProxy = null;
            }

            if (m_mongoGraphProxy != null)
            {
                m_mongoGraphProxy.Dispose();
                m_mongoGraphProxy = null;
            }
        }
        #endregion
    }
}
